#include "il2cpp-config.h"

#if IL2CPP_TARGET_POSIX

#include "os/Directory.h"
#include "os/ErrorCodes.h"
#include "os/File.h"
#include "os/Posix/Error.h"
#include "utils/DirectoryUtils.h"
#include "utils/PathUtils.h"
#include "utils/StringUtils.h"
#include <assert.h>
#include <errno.h>
#include <dirent.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/types.h>

namespace il2cpp
{
namespace os
{

std::string Directory::GetCurrent (int *error)
{
	char buf[PATH_MAX+1];
	// Note: not all implementations would allocate a buffer when passing 0 to getcwd, as we used to do.
	// this does *not* seem to be part of the POSIX spec:
	// http://pubs.opengroup.org/onlinepubs/000095399/functions/getcwd.html
	char* cwd = getcwd (buf, PATH_MAX+1);

	if(cwd == NULL)
	{
		*error = FileErrnoToErrorCode(errno);
		return std::string();
	}

	std::string directory(cwd);

	*error = kErrorCodeSuccess;
	return directory;
}

bool Directory::SetCurrent (const std::string& path, int *error)
{
	const int ret = chdir (path.c_str());

	if(ret == -1)
	{
		*error = FileErrnoToErrorCode(errno);
		return false;
	}

	*error = kErrorCodeSuccess;
	return true;
}

bool Directory::Create (const std::string& path, int *error)
{
	const int ret = mkdir (path.c_str(), 0777);

	if(ret == -1)
	{
		*error = PathErrnoToErrorCode (path, errno);
		return false;
	}

	*error = kErrorCodeSuccess;
	return true;
}

bool Directory::Remove (const std::string& path, int *error)
{
	const int ret = rmdir (path.c_str());

	if(ret == -1)
	{
		*error = PathErrnoToErrorCode (path, errno);
		return false;
	}

	*error = kErrorCodeSuccess;
	return true;
}

static void DirectoryGlob (DIR *dir, const std::string& pattern, std::set<std::string>& result)
{
	if(pattern.empty())
		return;

	std::string matchPattern = il2cpp::utils::CollapseAdjacentStars(pattern);

	struct dirent *entry;

	while((entry = readdir (dir)) != NULL)
	{
		const std::string filename(entry->d_name);

		if(!il2cpp::utils::Match(filename, matchPattern))
			continue;

		result.insert(filename);
	}
}

static bool DirectoryGlob (const std::string& directoryPath, const std::string& pattern, std::set<std::string>& result, int* error)
{
	DIR* dir = opendir(directoryPath.c_str());

	if(dir == NULL)
	{
		*error = PathErrnoToErrorCode (directoryPath, errno);
		return false;
	}

	DirectoryGlob(dir, pattern, result);

	closedir(dir);

	return true;
}

std::set<std::string> Directory::GetFileSystemEntries (const std::string& path, const std::string& pathWithPattern, int32_t attributes, int32_t mask, int* error)
{
	const std::string directoryPath(il2cpp::utils::PathUtils::DirectoryName(pathWithPattern));
	const std::string pattern(il2cpp::utils::PathUtils::Basename(pathWithPattern));

	std::set<std::string> globResult;

	if(DirectoryGlob(directoryPath, pattern, globResult, error) == false)
		return std::set<std::string>();

	if(il2cpp::utils::StringUtils::EndsWith(pattern, ".*"))
	{
		/* Special-case the patterns ending in '.*', as
		 * windows also matches entries with no extension with
		 * this pattern.
		 */

		 if(DirectoryGlob(directoryPath, pattern.substr(0, pattern.length()-2), globResult, error) == false)
			return std::set<std::string>();
	}

	std::set<std::string> result;

	for(std::set<std::string>::const_iterator it = globResult.begin(), end = globResult.end(); it != end; ++it)
	{
		const std::string& filename = *it;

		if(filename == "." || filename == "..")
			continue;

		const std::string path(directoryPath + IL2CPP_DIR_SEPARATOR + filename);

		int attributeError;
		const int32_t pathAttributes = static_cast<int32_t>(File::GetFileAttributes(path, &attributeError));

		if(attributeError != kErrorCodeSuccess)
			continue;

		if((pathAttributes & mask) == attributes)
			result.insert(path);
	}


	*error = kErrorCodeSuccess;
	return result;
}

}
}

#endif
